package com.ibeeking.gateway.open.config.filter;

import com.ibeeking.gateway.open.constant.LogUtils;
import com.ibeeking.gateway.open.constant.ThreadContextCache;
import com.ibeeking.gateway.open.service.IResourceService;
import com.ibeeking.found.common.constants.HeaderNounConstant;
import com.ibeeking.found.common.enums.LoginRoleEnum;
import com.ibeeking.nematos.constants.enums.LogConstant;
import com.ibeeking.nematos.utils.data.StringUtils;
import com.alibaba.fastjson.JSONObject;
import com.nimbusds.jose.JWSObject;
import lombok.SneakyThrows;
import org.apache.logging.log4j.ThreadContext;
import org.apache.logging.log4j.util.Strings;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.util.List;

/**
 * 黑名单token过滤器
 *
 * @Author ibeeking
 */
@Component
@RefreshScope
public class GlobalFilter implements org.springframework.cloud.gateway.filter.GlobalFilter, Ordered {

    @Resource
    private IResourceService resourceServiceImpl;

    @SneakyThrows
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        LogUtils.debug("收到请求,url:{},method:{},request:{}", request.getURI(), request.getMethod(), request);
        //解析token
        JSONObject tokenObject = analysisToken(request.getHeaders().getFirst(HeaderNounConstant.AUTHORIZATION));
        ServerHttpRequest newRequest = addHeader(request, tokenObject);
        if (null != tokenObject && !resourceServiceImpl.checkApi(tokenObject, request.getPath().value(), newRequest)) {
            ServerHttpResponse response = exchange.getResponse();
            response.setStatusCode(HttpStatus.FORBIDDEN);
            return response.setComplete();
        }
        try {
            return chain.filter(exchange.mutate().request(newRequest).response(exchange.getResponse()).build());
        } finally {
            ThreadContext.clearAll();
        }
    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }

    /**
     * 添加请求头信息
     *
     * @param request
     * @return
     * @throws ParseException
     */
    private ServerHttpRequest addHeader(ServerHttpRequest request, JSONObject tokenObject) throws ParseException {
        ServerHttpRequest build = request.mutate().build();
        HttpHeaders headers = new HttpHeaders();
        headers.putAll(request.getHeaders());
        headers.add(LogConstant.TRACE_ID, request.getId());
        String industry;
        String userId;
        String userName;
        String amDemoUser;
        String loginSystem;
        String loginRole;
        String tenantId;
        if (null != tokenObject) {
            industry = tokenObject.get(HeaderNounConstant.INDUSTRY).toString();
            userId = tokenObject.get(HeaderNounConstant.USER_ID).toString();
            userName = tokenObject.get(HeaderNounConstant.USER_NAME).toString();
            amDemoUser = tokenObject.get(HeaderNounConstant.AM_DEMO_USER).toString();
            tenantId = tokenObject.get(HeaderNounConstant.TENANT_ID).toString();
            List<String> roles = (List<String>) tokenObject.get(HeaderNounConstant.AUTHORITY_CLAIM_NAME);
            loginSystem = roles.get(0);
            if (roles.size() > 1) {
                loginRole = roles.get(1);
            } else {
                loginRole = roles.get(0);
            }
        } else {
            industry ="";
            userId = "";
            userName = "";
            amDemoUser = "false";
            tenantId = "";
            loginSystem = LoginRoleEnum.OPEN.name();
            loginRole = LoginRoleEnum.OPEN.name();
        }
        headers.add(HeaderNounConstant.USER_ID, userId);
        headers.add(HeaderNounConstant.LOGIN_SYSTEM, loginSystem);
        headers.add(HeaderNounConstant.LOGIN_ROLE, loginRole);
        headers.add(HeaderNounConstant.TENANT_ID, tenantId);
        headers.add(HeaderNounConstant.INDUSTRY, industry);
        headers.add(HeaderNounConstant.AM_DEMO_USER, amDemoUser);
        try {
            headers.add(HeaderNounConstant.USER_NAME, URLEncoder.encode(userName, StandardCharsets.UTF_8.name()));
        } catch (UnsupportedEncodingException e) {
            LogUtils.error("中文编码异常[{}]", userName);
        }
        //往本地线程添加信息
        ThreadContextCache.threadContext(build.getHeaders().getFirst(HeaderNounConstant.TENANT_ID),
                build.getId(), build.getHeaders().getFirst(HeaderNounConstant.LOGIN_SYSTEM),
                build.getHeaders().getFirst(HeaderNounConstant.LOGIN_ROLE),
                build.getHeaders().getFirst(HeaderNounConstant.USER_NAME),
                build.getHeaders().getFirst(HeaderNounConstant.USER_ID),
                build.getHeaders().getFirst(HeaderNounConstant.AM_DEMO_USER),
                industry
        );
        return new ServerHttpRequestDecorator(build) {
            @Override
            public HttpHeaders getHeaders() {
                return headers;
            }
        };
    }

    /**
     * 解析token
     *
     * @param token token
     * @return
     * @throws ParseException
     */
    private JSONObject analysisToken(String token) throws ParseException {
        JSONObject jsonObject = null;
        if (StringUtils.isNotBlank(token)) {
            String payload = JWSObject.parse(token.replace(HeaderNounConstant.JWT_PREFIX, Strings.EMPTY)).getPayload().toString();
            jsonObject = JSONObject.parseObject(payload);
        }
        return jsonObject;
    }
}
